package com.springbootblog.utils;

import com.alibaba.fastjson.JSON;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class ElasticsearchUtils
{
    // ==========================================================================================================
    // 索引操作 start
    /**
     * 创建索引
     * @param restHighLevelClient
     * @throws IOException
     */
    public static @NotNull Map<String, Object> createIndex(RestHighLevelClient restHighLevelClient, String indexName) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            // 查看是否创建成功
            if(!response.isAcknowledged())
            {
                result.put("code", -1);
                result.put("msg", "创建失败！");
                return result;
            }
            // 查看返回对象
            // System.out.println(response);
            restHighLevelClient.close();
            result.put("code", 1);
            result.put("msg", "操作成功！");
            return result;
        }
        catch (Exception ex)
        {
            result.put("code", -2);
            result.put("msg", "创建失败！");
            return result;
        }
    }

    /**
     * 获取索引，并判断其是否存在
     * @param restHighLevelClient
     * @throws IOException
     */
    public static @NotNull Map<String, Object> indexIsExists(RestHighLevelClient restHighLevelClient, String indexName) throws IOException
    {
        Map<String,Object> result = new HashMap<>(3);
        try
        {
            GetIndexRequest request = new GetIndexRequest(indexName);
            boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
            // 索引是否存在
            System.out.println(exists);
            restHighLevelClient.close();
            result.put("code", 1);
            result.put("msg", "操作成功！");
            result.put("exists", exists);
            return result;
        }
        catch (Exception ex)
        {
            System.out.println(indexName);
            result.put("code", 2);
            result.put("msg", "操作成功！");
            result.put("exists", false);
            return result;
        }

    }

    /**
     * 索引删除
     * @param restHighLevelClient
     * @param indexName
     * @throws IOException
     */
    public static @NotNull Map<String, Object> deleteIndex(RestHighLevelClient restHighLevelClient, String indexName) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            DeleteIndexRequest request = new DeleteIndexRequest(indexName);
            AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
            // 是否删除成功
            // System.out.println(response.isAcknowledged());
            if(!response.isAcknowledged())
            {
                result.put("code", -2);
                result.put("msg", "删除失败！");
                return result;
            }
            restHighLevelClient.close();
            result.put("code", 1);
            result.put("msg", "操作成功！");
            return result;
        }
        catch (Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误，删除失败！");
            return result;
        }
    }

    // ==========================================================================================================
    // 文档操作 start
    // 添加文档(先创建一个User实体类，添加fastjson依赖)
    /**
     * 单个添加文档
     * @param object                实体类对象
     * @param indexName             索引名称
     * @param restHighLevelClient   Elasticsearch对象
     * @throws IOException
     */
    public static @NotNull Map<String, Object> addDocument(Object object, String indexName, RestHighLevelClient restHighLevelClient, String id, String time) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            // 创建请求 添加文档请求
            IndexRequest request = new IndexRequest(indexName);
            // 制定规则 PUT /liuyou_index/_doc/1
            // 设置文档ID
            request.id(id);
            // 设置 超时时间
            request.timeout(time); //request.timeout(TimeValue.timeValueMillis(1000));
            // 将我们的数据放入请求中
            request.source(JSON.toJSONString(object), XContentType.JSON);
            // 客户端发送请求，获取响应的结果
            IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            // 获取建立索引的状态信息 CREATED
            System.out.println(response.status());
            // 查看返回内容 IndexResponse[index=liuyou_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
            System.out.println(response);
            result.put("code", 1);
            result.put("msg", "操作成功！");
            return result;
        }
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误，添加失败！");
            return result;
        }
    }

    /**
     * 文档信息的获取 根据 id
     * @param restHighLevelClient
     * @param indexName
     * @param id
     * @throws IOException
     */
    public static @NotNull Map<String, Object> getDocument(RestHighLevelClient restHighLevelClient, String indexName, String id) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            GetRequest request = new GetRequest(indexName,id);
            GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
            // 打印文档内容
            System.out.println("getSourceAsString:"+response.getSourceAsString());
            // 返回的全部内容和命令是一样的
            System.out.println("request:"+request);
            restHighLevelClient.close();
            result.put("code", 1);
            result.put("object", response.getSourceAsString());
            return result;
        }
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误，获取失败！");
            return result;
        }
    }


    /**
     * 获取文档，判断是否存在 get /liuyou_index/_doc/1
     * @param restHighLevelClient
     * @param indexName
     * @param id
     * @throws IOException
     */
    public static @NotNull Map<String, Object> documentIsExists(RestHighLevelClient restHighLevelClient, String indexName, String id) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            GetRequest request = new GetRequest(indexName, id);
            // 不获取返回的 _source的上下文了
            request.fetchSourceContext(new FetchSourceContext(false));
            request.storedFields("_none_");
            boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
            System.out.println("exists:"+exists);
            result.put("code", 1);
            result.put("exists", exists);
            return result;
        }
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误!");
            return result;
        }

    }

    /**
     * 更新文档内容
     * @param restHighLevelClient
     * @param indexName
     * @param id
     * @param object
     * @throws IOException
     */
    public static @NotNull Map<String, Object> updateDocument(RestHighLevelClient restHighLevelClient, String indexName, String id, Object object) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {//*/
            UpdateRequest request = new UpdateRequest(indexName, id);
            request.timeout("60s");
            request.doc(JSON.toJSONString(object),XContentType.JSON);
            UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
            // 查看是否更新成功
            System.out.println(response.status());
            restHighLevelClient.close();
            result.put("code", 1);
            result.put("status", response.status());
            return result;
        }
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误!");
            return result;
        }//*/
    }

    /**
     * 删除文档
     * @param restHighLevelClient
     * @param indexName
     * @param id
     * @param time
     * @throws IOException
     */
    public static @NotNull Map<String, Object> deleteDocument(RestHighLevelClient restHighLevelClient, String indexName, String id, String time) throws IOException
    {
        Map<String,Object> result = new HashMap<>(2);
        try
        {
            DeleteRequest request = new DeleteRequest(indexName, id);
            request.timeout(time);
            DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
            // 查看是否删除成功 OK/NOT_FOUND
            System.out.println(response.status());
            result.put("code", 1);
            result.put("status", response.status());
            return result;
        }
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误!");
            return result;
        }
    }

    /**
     * 条件查询
     * @param restHighLevelClient
     * @param indexName
     * @param time
     * @throws IOException
     * SearchRequest 搜索请求
     * SearchSourceBuilder 条件构造
     * HighlightBuilder 高亮
     * TermQueryBuilder 精确查询
     * MatchAllQueryBuilder
     * xxxQueryBuilder ...
     */
    public static Map<String, Object> mapSearch(@NotNull RestHighLevelClient restHighLevelClient, String indexName, Long time, String searchString,Integer page, String[] field) throws IOException
    {
        Map<String,Object> result = new HashMap<>(6);
        /*try
        {//*/
            // 1.创建查询请求对象
            SearchRequest searchRequest = new SearchRequest(indexName);
            // 2.构建搜索条件
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            // (1)查询条件 使用QueryBuilders工具类创建
            // 精确查询
            // TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "liuyou");
            // 匹配查询
            // MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
            // System.out.println(searchString);
            // 单字段 匹配查询
            // MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("content",searchString);
            // 多字段查询
            BoolQueryBuilder matchQueryBuilder = QueryBuilders.boolQuery();

            for (String item : field) {
                matchQueryBuilder.should(QueryBuilders.matchQuery(item, searchString));
            }
            // 设置循环中每个字段的权重，这里所有字段权重相同
            matchQueryBuilder.minimumShouldMatch(1);
            // MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery(searchString,"content","arttitle","artdesc");
            // 分页
            Integer pageSize = 9;
            // 计算分页信息
            Map<String, Integer> pageInfo = Function.getStartNumByPage(page, pageSize);
            Integer startPage = pageInfo.get("page");
            Integer showNum = pageInfo.get("pageSize");
            // elasticsearch 分页
            searchSourceBuilder.from(startPage);
            searchSourceBuilder.size(showNum);
            searchSourceBuilder.timeout(new TimeValue(time, TimeUnit.SECONDS));
            // searchSourceBuilder.timeout(time);
            // (2)其他<可有可无>：（可以参考 SearchSourceBuilder 的字段部分）
            // 设置高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 循环高亮字段
            for (String s : field)
            {
                highlightBuilder.field(s);
            }
            // 设置高亮标签
            highlightBuilder.preTags("<font style='color:red;'>").postTags("</font>");
            searchSourceBuilder.highlighter(highlightBuilder);

            // (3)条件投入
            searchSourceBuilder.query(matchQueryBuilder);
            // 3.添加条件到请求
            searchRequest.source(searchSourceBuilder);
            // 4.客户端查询请求
            SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 5.查看返回结果
            SearchHits hits = search.getHits();
            // 6.获取查询结果集总数（未分页的情况下）
            long number = hits.getTotalHits().value;
            // 页码数
            int articleNum = (int) (Math.ceil((float)number / (float)pageSize) - 1) + 1;
            // 高亮字段 解析结果
            List<Object> list = new ArrayList<>();
            for (SearchHit hit: hits.getHits())
            {
                //解析高亮字段
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                //System.out.println(hit);
                //获取原字段
                Map<String, Object> oldUser = hit.getSourceAsMap();
                //将高亮替换原字段内容
                StringBuilder stringBuilder = new StringBuilder();
                // ================================================================
                // 第一个高亮字段
                HighlightField arttitle = highlightFields.get("arttitle");
                if (arttitle!=null)
                {
                    Text[] texts = arttitle.fragments();
                    for (Text t:texts)
                    {
                        stringBuilder.append(t);
                    }
                    oldUser.put("arttitle",stringBuilder.toString());
                }
                // ================================================================
                // 第二个高亮字段
                HighlightField artdesc = highlightFields.get("artdesc");
                if (artdesc!=null)
                {
                    Text[] texts = artdesc.fragments();
                    for (Text t:texts)
                    {
                        stringBuilder.append(t);
                    }
                    oldUser.put("artdesc",stringBuilder.toString());
                }
                list.add(oldUser);
            }//*/
            result.put("code", 1);
            result.put("msg", "操作成功！");
            result.put("articleShow", list);
            result.put("cateName", searchString);
            result.put("articlePage", articleNum);
            result.put("cateList", new ArrayList<String>());
            return result;
        /*}
        catch(Exception ex)
        {
            result.put("code", -1);
            result.put("msg", "系统错误!");
            return result;
        }//*/
    }

    /**
     * elasticsearch 批量插入数据
     * @param restHighLevelClient
     * @param list
     * @param time
     * @throws IOException
     */
    public static Map<String,Object> batchAddData(RestHighLevelClient restHighLevelClient, @NotNull ArrayList<Map<String,Object>> list, String time, String indexName) throws IOException
    {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout(time);
        // 批量请求处理
        for (int i = 0; i < list.size(); i++)
        {
            bulkRequest.add(
                    // 这里是数据信息
                    new IndexRequest(indexName)
                            .id(list.get(i).get("id").toString()) // 没有设置id 会自定生成一个随机id
                            .source(JSON.toJSONString(list.get(i).get("info")),XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        // 批量添加状态
        System.out.println(bulk.status());
        Map<String,Object> result = new HashMap<>(2);
        result.put("code", 1);
        result.put("status", bulk.status());
        return result;
    }

}
